package org.jrfe.sys;

import org.jrfe.JRFE;
import org.jrfe.gfx.ColorRGBA;
import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.PixelFormat;

/**
 * System class for creating and destroying display, 
 * Main loop throw {@link JRFEContext} and timer functions if engine loop is used
 * @author Kalvis Freimanis
 */
public abstract class JRFESystem {

	/**
	 * Engine Settings
	 */
	protected static JRFESettings settings;
	
	/**
	 * Engine Context
	 */
	protected static JRFEContext context;
	
	/**
	 * Frames per second limit ( -1 no limit)
	 */
	protected static int fps = -1;
	
	/**
	 * Passed time between frames in MS
	 */
	protected static long delta = 0;

	
	/**
	 * Display clear color
	 */
	protected static ColorRGBA clearColor = new ColorRGBA(0.1f, 0.1f, 1f, 0);
	
	/**
	 * Set Engine Context
	 * @param context {@link JRFEContext} to set
	 */
	public static void setJRFEContext(JRFEContext context) {
		JRFESystem.context = context;
	}
	
	/**
	 * Set Display clear color
	 * @param clearColor color object
	 */
	public static void setClearColor(ColorRGBA clearColor) {
		JRFESystem.clearColor = clearColor;
	}

	public static boolean createDisplay(JRFESettings settings) {
		return createDisplay(settings, new PixelFormat());
	}
	
	/**
	 * Create Display with specified settings
	 * @param settings {@link JRFESettings}
	 * @return true if dispaly created
	 */
	public static boolean createDisplay(JRFESettings settings,PixelFormat px) {
		JRFESystem.settings = settings;
		px = px.withSamples(settings.antialiasing);
		DisplayMode chosenMode = null;

		try {
			DisplayMode[] modes = Display.getAvailableDisplayModes();

			for (int i = 0; i < modes.length; i++) {
				if ((modes[i].getWidth() == settings.width)
						&& (modes[i].getHeight() == settings.height)
						&& (modes[i].getBitsPerPixel() == settings.bpp)) {
					chosenMode = modes[i];
					break;
				}
			}
		} catch (LWJGLException e) {
			Sys.alert("Error", "Unable to determine display modes.");
			return false;
		}

		if (chosenMode == null) {
			Sys.alert("Error", "Unable to find appropriate display mode.");
			return false;
		}

		try {

			fps = settings.fpslimit;
			
			Display.setDisplayMode(chosenMode);
			Display.setFullscreen(settings.fullscreen);
			Display.setTitle(settings.title);
			Display.setVSyncEnabled(settings.vsync);
			Display.setLocation(settings.locx, settings.locy);
			//Display.create();
			Display.create(px);
	
		} catch (LWJGLException e) {
			e.printStackTrace();
			Sys.alert("Error", e.getMessage());
			return false;
		}

		return true;
	}

	/** 
	 * Destroy created Display
	 */
	public static void destroyDisplay() {
		Display.destroy();
	}

	/** 
	 * Start engine loop 
	 * And run until display close is requested or 
	 * one of {@link JRFEContext} return false
	 */
	public static void start() {

		if (context == null) {
			Sys.alert("Error", "JRFEContext not set.");
			throw new RuntimeException("JRFEContext not set");
		}

		if (context.initialize()) {
			boolean running = true;
			long time = 0;
			long lastTime = getTime();

			// OpenGL Screen setup code for 2d projection
			GL11.glDisable(GL11.GL_DEPTH_TEST);

			GL11.glEnable(GL11.GL_CULL_FACE);
			GL11.glCullFace(GL11.GL_BACK);
			GL11.glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);

			GL11.glMatrixMode(GL11.GL_PROJECTION);
			GL11.glLoadIdentity();
			GL11.glOrtho(0, settings.width, 0, settings.height, 0, 1);
			GL11.glMatrixMode(GL11.GL_MODELVIEW);
			
			while (!Display.isCloseRequested() && running) {

				time = getTime();
				delta = time - lastTime;
				lastTime = time;
				if (fps > 0) {
					Display.sync(fps);
				}

				running = context.update();
				if (running) {
					// Clear buffer after each frame
					GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
					
					running = context.render();
				}

				Display.update();

			}
			
			context.cleanup();
		}

		// boolean running
	}

	/**
	 * Get Time between frames in milliseconds
	 * 
	 * @return delta time
	 */
	public static long getDeltaMS() {		
		return delta;
	
	}

	/**
	 * Get time between frames is seconds
	 * 
	 * @return delta time
	 */
	public static float getDelta() {
		return delta / 1000.f;
	}

	/**
	 * Get system time;
	 * 
	 * @return system time;
	 */
	public static long getTime() {
		return (Sys.getTime() * 1000) / Sys.getTimerResolution();
	}

}
